home *** CD-ROM | disk | FTP | other *** search
/ SuperHack / SuperHack CD.bin / CODING / GRAPHICS / VOXELTUT.ZIP / VOXEL.DOC < prev    next >
Encoding:
Text File  |  1996-05-06  |  9.2 KB  |  203 lines

  1.                              -= Voxel Renderer =-
  2.  
  3.   Files Included:
  4.  
  5.     VOXEL.C   - Watcom C/C++ Source code for heightfield render
  6.     VOXEL.EXE - Executable form of the source code
  7.     VOXEL.DOC - This documentation file
  8.  
  9.   Instructions:
  10.  
  11.     At your DOS prompt, type "VOXEL" and press enter. If you have a mouse
  12.   installed, use it to move around. If you do not, the screen will move on
  13.   its own.
  14.  
  15.   This executable is Win95 friendly.
  16.  
  17.  
  18.   How it works:
  19.  
  20.     To begin with, I set up 2 arrays. Each array is the width of the area I
  21.   want to render (in this case its 320 for mode 13h). In each index of the
  22.   array I keep height and color information. These buffers can be refered to
  23.   as a top buffer and bottom buffer. I refer to them as Buf1 and Buf2 in my
  24.   code (Buf1 would be the top buffer).
  25.  
  26.     To trace the area to be rendered, I texture map the triangle created by
  27.   the view system. I start with the line in the view system between the points
  28.   (x1,y1) and (x2,y2). This point is the maximum depth of rendering and is
  29.   also a special case. This is a line of constant z, so perspective handling
  30.   will only need to be handled once for this entire line. The perspective
  31.   transform I use can be found in any documentation on 3D renderind:
  32.  
  33.     y' = ScreenMidY - (Y * Distance) / Z
  34.  
  35.   We want to store these perspective transforms in our "top" buffer. To do
  36.   this, we have to stretch the (x1, y1),(x2, y2) line to the rendering width
  37.   (which is 320 in this case). We then count for the screen width (320), using
  38.   the texture value (the texture is our height map) as the Y in the perspective
  39.   transform. The Z value we use in the maximum depth used in rendering. We also
  40.   need the color at that particular point. For simplicity sake, we will use the
  41.   map color. Here is a piece of code to illistrate how this is done.
  42.  
  43.   for (x = 0; x < 320; x++)
  44.   {
  45.     topbuffer[x].y = 99 - (heightmap[xval >> 8 + yval & 0xff00]*Distance)/z;
  46.     topbuffer[x].c = heightmap[xval >> 8 + yval & 0xff00]
  47.     yval += ystep;
  48.     xval += xstep;
  49.   }
  50.  
  51.   We are using 24.8 fixed point for the texturing coordinates and using
  52.   a 256x256 map. As you may have figured out, this does not provide any
  53.   smoothing, which is ok. Since we are at the furthest point from the viewer
  54.   sampling every point looks the same as interpolating between heights (this
  55.   is why its a special case, along with writing to the "top" buffer).
  56.  
  57.   With the furthest line done, we are ready to handle the meat of the
  58.   rendering. The intermediate lines make up the greatest portion of the view
  59.   so we will take great care to make sure they look the best. For starters,
  60.   increment the texture values so they are one step closer to the viewer.
  61.   You are now ready to enter the intermediate section. For this section you
  62.   loop from the maximum depth - 1, to the nearest depth + 1. The basic process
  63.   for rendering this section is to read the map values, interpolate the heights
  64.   and colors, render the horizontal lines, increment the texture coordinates,
  65.   then repeat.
  66.  
  67.   Reading the map values for this section is a bit different than the furthest
  68.   line case. For this we only want to do a perspective transform on the first
  69.   point in each heightfield we reach. This process is actually a little trickey,
  70.   but still quite fast. To do this properly, we must keep a secondary list that
  71.   contains the X values of the points we actually perspective transform.
  72.   Ideally these points will be 2-4 pixels apart, maybe more. We also need a
  73.   way to tell if we have moved to a new index in our map, which tells us to
  74.   record the X value. This is actually fairly simple. We keep an old value
  75.   and a current value. When the current value is the same as the old value,
  76.   we are at the same map location. To simplify this even more, you can keep
  77.   old and current values as an offset instead of coordinates. The thought might
  78.   arise concering what use initialize the old value to before the loop begins.
  79.   This is simple. Initialize it to a number that is impossible for the index
  80.   to reach, such as -1. Since I know you are throroughly confused at this point
  81.   I will give you a small piece of code to illistrate what is going on.
  82.  
  83.  
  84.   oldindex = -1;  // initialize old value
  85.   xindex = 0;     // initialize x index table counter
  86.  
  87.   for (x = 0; x < 320; x++)
  88.   {
  89.     index = (xval >> 8) + (yval & 0xff00);  // get curent index value
  90.     if (index != oldindex)
  91.     {
  92.       bottombuffer[x].y = 99 - (heightmap[index]*Distance)/z;
  93.       bottombuffer[x].c = heightmap[index]
  94.  
  95.       xtable[xindex] = x;  // record X value
  96.       xindex++;
  97.  
  98.       oldindex = index;
  99.     }
  100.     yval += ystep;
  101.     xval += xstep;
  102.   }
  103.  
  104.  
  105.   Once that loop is complete, we have a list of the columns we did a
  106.   perspective transform on, and we skipped any duplicate transforms. Also
  107.   notice that we are saving to the BottomBuffer instead of the top. When
  108.   doing the intermidate lines, we always store to the bottom buffer. A subtle
  109.   point to this process is what if the right most point (x = 319) is still
  110.   equal to the old index? We won't get a closing value in our xtable list
  111.   resulting in fluctuating values making for an ugly display. The easiest way
  112.   to fix this is to modify the IF statement to read:
  113.  
  114.     if (index != oldindex) || (x = 319)
  115.  
  116.   This will always make sure we have an ending (the -1 always makes sure we
  117.   have a beginning).
  118.  
  119.   When that loop is finished, you are ready to perform the magic. This little
  120.   step is what makes the land so smooth. You must interpolate the height and
  121.   the color between the xtable points. A simple linear interpolation is all
  122.   thats necessary to provide a convincing look. Since you know the number of
  123.   xtable entries, and you know the x values at each of those entries,
  124.   interpolatoin is straight forward. During the interpolation, be sure to
  125.   store the inbetween values back into the bottom buffer. Here is how I do it.
  126.  
  127.   for (i = 0; i < (xindex - 1); i++)
  128.   {
  129.     cval = bottomtable[xtable[i]].c << 8;
  130.     yval = bottomtable[xtable[i]].y << 8;
  131.     cstep = ((bottomtable[xtable[i+1]].c - bottomtable[xtable[i]].c) << 8) /
  132.             (xtable[i+1] - xtable[i]);
  133.     ystep = ((bottomtable[xtable[i+1]].y - bottomtable[xtable[i]].y) << 8) /
  134.             (xtable[i+1] - xtable[i]);
  135.  
  136.     for (j = xtable[i]+1; j < xtable[i+1]; j++)
  137.     {
  138.        cval += cstep;
  139.        yval += ystep;
  140.        bottomtable[j].y = yval >> 8;
  141.        bottomtable[j].c = cval >> 8;
  142.     }
  143.   }
  144.  
  145.   There are a few subtle points in this loop. The first is i looping from
  146.   0 to xindex - 2 ( < xindex - 1 ). This is because we sometimes index xtable
  147.   with an i+1. Looping to xindex - 1 would go beyond the bounds of the array
  148.   esulting in bogus data. The next point is j looping from xtable[i]+1. This
  149.   is because we already know the value at xtable[i]. No need to re-store the
  150.   value.
  151.  
  152.   The difficult part of the routine is now finished. From here, you call a
  153.   function to draw vertical lines from the top buffer to the bottom buffer.
  154.   The function should do some sort of color interpolation. Also note that
  155.   this routine should only draw a vertical line if the top buffer value
  156.   is less than the bottom buffer value.
  157.  
  158.   After you draw the vertical lines, copy the values in the bottom buffer
  159.   to the top buffer. This will prime the tables for the next time around.
  160.   Increment your texture values once again, then your loop should continue.
  161.  
  162.   Once you have reached the end of that loop, you are ready for the nearest
  163.   line. This line is also a special case, but is much easier to handle than
  164.   the other two cases. Entering this loop, we have the top buffer set with
  165.   the last values we calculated, and we are at the closest point to the viewer
  166.   in the texture. In the last loop, we go just like the first loop (furthest
  167.   depth), bu instead of doing a perspective transform to get the height, just
  168.   store the value 199. Since this is the bottom of the screen, it automatically
  169.   gets rid of any holes in the rendering. You still need to interpolate the
  170.   texture values though in order to get the color. When looping, store the
  171.   values in the bottom buffer. Here is code on how to do it.
  172.  
  173.   for (x = 0; x < 320; x++)
  174.   {
  175.     bottombuffer[x].y = 199;
  176.     bottombuffer[x].c = heightmap[xval >> 8 + yval & 0xff00]
  177.     yval += ystep;
  178.     xval += xstep;
  179.   }
  180.  
  181.   With the last loop complete, do another call to the vertical line drawing
  182.   routine. Once that is finished, your landscape is rendered.
  183.  
  184.  
  185.   Notes:
  186.  
  187.     If you have trouble following this explination, I have included full
  188.   working source code of this method. There are a few optimizations in the
  189.   source code that I haven't discussed here.
  190.  
  191.     There are also several shortcomings to this method. The biggest deals
  192.   with rotations. When you rotate to certain angles you get terrible peaks
  193.   on quite a few portions of the display. I know the cause, but I don't
  194.   feel like explaining it (I've typed enough already). You can probably
  195.   get rid of it by doing a conditional rendering based on the slope of the
  196.   lines when doing the texture mapping.
  197.  
  198.  
  199.   If you have any questions or comments, please feel free to contact me.
  200.  
  201.   Alex Chalfin
  202.   achalfin@uceng.uc.edu
  203.